<template>
    <div class="wrapper">
        <div id="container" ref="container"></div>
        <div class="btn-list">
            <button class="btn" @click="addNode({}, true)">add</button>
            <button class="btn" @click="format">格式化</button>
            <button class="btn" @click="save">保存</button>
        </div>
    </div>
</template>

<script>
import { Graph, Model } from "@antv/x6";
import { DagreLayout } from "@antv/layout";
import * as mock from "../../mock/mock.js";

export default {
    name: "",
    data () {
        return {
            graph: null,
            nodes: [],
            edges: [],
            node_width: 160,
            node_height: 30,
            portAttrs: {
                circle: {
                    magnet: true,
                    r: 5,
                    stroke: "#31d0c6",
                    strokeWidth: 2,
                    fill: "#fff",
                    style: {
                        visibility: "hidden"
                    }
                }
            },
            currDir: "",
            handledEdges: [], // 更新连线时，存储已经处理过的边
            handledNodes: [] // 更新连线时，存储已经处理过的节点
        };
    },
    methods: {
        // mock一些数据
        getData () {
            this.graphData = JSON.parse(localStorage.getItem("graphData"));
            if (this.graphData) {
                this.nodes = this.graphData.nodesArr;
                this.edges = this.graphData.edgesArr;
            }
            // this.nodes = mock.mockData3.nodes;
            // this.edges = mock.mockData3.edges;
        },
        // graph初始化
        initGraph () {
            this.graph = new Graph({
                container: this.$refs.container,
                async: true,
                width: 1300,
                height: 800,
                grid: {
                    size: 10,
                    visible: true
                },
                panning: true,
                snapline: true,
                connecting: {
                    allowBlank: false
                }
            });
            // 注册事件
            this.registEvents();
            // 新增节点
            this.nodes.forEach(item => {
                this.addNode(item);
            });
            // 新增边
            this.addEdge();
            // 通过拐点和路由更新连线
            this.updateEdgesByVerticesAndRouter();
        },
        // 增加节点
        addNode (item, isNew) {
            if (item.loc) {
                const coord = item.loc.split(" ");
                item.x = parseInt(coord[0]);
                item.y = parseInt(coord[1]);
            }
            this.graph.addNode({
                id: item.id || `node${this.nodes.length + 1}`,
                x: item.x || 0,
                y: item.y || 0,
                width: item.width || this.node_width,
                height: item.height || this.node_height,
                label: item.label || `节点${this.nodes.length + 1}`,
                attrs: {
                    body: {
                        fill: "#f5f5f5",
                        stroke: "#d9d9d9",
                        strokeWidth: 1
                    },
                    label: {
                        fontSize: 12
                    }
                },
                ports: {
                    groups: {
                        top: {
                            attrs: this.portAttrs,
                            zIndex: 10,
                            position: {
                                name: "top"
                            }
                        },
                        right: {
                            attrs: this.portAttrs,
                            zIndex: 10,
                            position: {
                                name: "right"
                            }
                        },
                        bottom: {
                            attrs: this.portAttrs,
                            zIndex: 10,
                            position: {
                                name: "bottom"
                            }
                        },
                        left: {
                            attrs: this.portAttrs,
                            zIndex: 10,
                            position: {
                                name: "left"
                            }
                        }
                    },
                    items: [
                        { id: "top", group: "top" },
                        { id: "right", group: "right" },
                        { id: "bottom", group: "bottom" },
                        { id: "left", group: "left" }
                    ]
                }
            });
            isNew && this.nodes.push(item);
        },
        // 新增边
        addEdge () {
            this.edges.forEach(edge => {
                this.graph.addEdge({
                    source: edge.source,
                    target: edge.target,
                    router: {
                        name: "manhattan",
                        args: {
                            padding: 8
                        }
                    },
                    zIndex: 1
                });
            });
        },
        // Dagre布局
        setDagreLayout () {
            const config = {};
            if (this.currDir === "LR") {
                config.ranksep = 70;
                config.nodesep = 20;
            } else if (this.currDir === "TB") {
                config.ranksep = 20;
                config.nodesep = 60;
            }
            const dagreLayout = new DagreLayout({
                type: "dagre",
                rankdir: this.currDir,
                ranksep: config.ranksep,
                nodesep: config.nodesep
            });
            const nodesArr = this.graph.getNodes();
            const nodes = nodesArr.map(node => ({ id: node.id, label: node.label }));
            const edges = this.graph.getEdges();
            // Dagre布局
            const model = dagreLayout.layout({ nodes, edges });
            // 各节点调整位置
            nodesArr.forEach(node => {
                const dagreNode = model.nodes.find(val => val.id === node.id);
                if (dagreNode) {
                    node.position(dagreNode.x, dagreNode.y);
                }
            });

            // 更新连线
            this.updateEdgesByVerticesAndRouter();
        },
        // 更新连线
        updateEdgesByVerticesAndRouter () {
            // 重置已经处理过的边
            this.handledEdges = [];
            // 重置所有edge的拐点和路由
            this.graph.getEdges().forEach(edge => edge.setVertices([]).setRouter({
                name: "manhattan",
                args: {
                    padding: 8
                }
            }));
            this.graph.getNodes().forEach(curNode => {
                const curNodePos = this.alterNodeConter(curNode);

                // 获取当前节点的输出边、输入边
                const outingEdges = this.graph.getOutgoingEdges(curNode);
                const comingEdges = this.graph.getIncomingEdges(curNode);

                // 处理outingEdges
                if (outingEdges && outingEdges.length) {
                    // 有多个输出边
                    if (outingEdges.length > 1) {
                        this.handleOutingEdges(curNodePos, outingEdges, curNode);
                    }
                }
                // 处理comingEdges
                if (comingEdges && comingEdges.length) {
                    // 有多条出入边
                    if (comingEdges.length > 1) {
                        this.handleComingEdges(curNodePos, comingEdges, curNode);
                    }
                }
            });
        },
        // 处理outingEdges
        handleOutingEdges (sourceNodePos, outingEdges, sourceNodeCell) {
            // 处理过的node
            const handledNodes = [];
            // 存储targetNode位于sourceNode什么位置
            const allDirNodes = [
                { dir: "bottom", nodes: [] },
                { dir: "right", nodes: [] },
                { dir: "top", nodes: [] },
                { dir: "left", nodes: [] }
            ];
            // 遍历outingEdges，获取所有targetNodes位于sourceNode什么位置
            outingEdges.forEach(edge => {
                const targetNode = edge.getTargetCell();
                const targetNodePos = this.alterNodeConter(targetNode);
                const { x, y } = targetNodePos;

                if (targetNodePos.y - sourceNodePos.y >= this.node_height + 40) { // 在下面
                    allDirNodes[0].nodes.push({ targetNode, edge, x, y });
                }
                if (targetNodePos.x - sourceNodePos.x >= this.node_width + 40) { // 在右边
                    allDirNodes[1].nodes.push({ targetNode, edge, x, y });
                }
                if (sourceNodePos.y - targetNodePos.y >= this.node_height + 40) { // 在上面
                    allDirNodes[2].nodes.push({ targetNode, edge, x, y });
                }
                if (sourceNodePos.x - targetNodePos.x >= this.node_width + 40) { // 在左边
                    allDirNodes[3].nodes.push({ targetNode, edge, x, y });
                }
            });
            // 处理这些连线数据
            this.handleSpecDirEdges(sourceNodePos, allDirNodes, handledNodes, "targetNode", sourceNodeCell);
        },
        // 处理comingEdges
        handleComingEdges (targetNodePos, comingEdges, targetNodeCell) {
            // 处理过的node
            const handledNodes = [];
            // 存储sourceNode位于什么位置
            const allDirNodes = [
                { dir: "top", nodes: [] },
                { dir: "left", nodes: [] },
                { dir: "bottom", nodes: [] },
                { dir: "right", nodes: [] }
            ];
            // 遍历comingEdges，获取所有sourceNodes位于targetNode什么位置
            comingEdges.forEach(edge => {
                const sourceNode = edge.getSourceCell();
                const sourceNodePos = this.alterNodeConter(sourceNode);
                const { x, y } = sourceNodePos;

                if (targetNodePos.y - sourceNodePos.y >= this.node_height + 40) { // 在上面
                    allDirNodes[0].nodes.push({ sourceNode, edge, x, y });
                }
                if (targetNodePos.x - sourceNodePos.x >= this.node_width + 40) { // 在左边
                    allDirNodes[1].nodes.push({ sourceNode, edge, x, y });
                }
                if (sourceNodePos.y - targetNodePos.y >= this.node_height + 40) { // 在下面
                    allDirNodes[2].nodes.push({ sourceNode, edge, x, y });
                }
                if (sourceNodePos.x - targetNodePos.x >= this.node_width + 40) { // 在右边
                    allDirNodes[3].nodes.push({ sourceNode, edge, x, y });
                }
            });
            // 处理这些连线
            this.handleSpecDirEdges(targetNodePos, allDirNodes, handledNodes, "sourceNode", targetNodeCell);
        },
        // 处理指定方向的连线
        handleSpecDirEdges (curNodePos, allDirNodes, handledNodes, nodeType, curNodeCell) {
            let longest = [];
            // 取最长那个方向数据
            if (allDirNodes.length > 0) {
                longest = allDirNodes.reduce((prev, curr) => prev.nodes.length >= curr.nodes.length ? prev : curr);
            }

            if (longest.nodes.length) {
                // 获取这些点中距离curNode最近的点
                const closest = this.getClosestPos(curNodePos, longest);
                // 给targetNode设置拐点和路由
                longest.nodes.forEach(item => {
                    this.setVerticesAndRouter(closest, item, longest.dir, curNodePos, nodeType, curNodeCell, longest.nodes);
                    // 处理过的node存起来
                    handledNodes.push(item);
                });
                allDirNodes.splice(allDirNodes.findIndex(i => i.dir === longest.dir), 1);
                allDirNodes.forEach(item => {
                    item.nodes = item.nodes.reduce((prev, curr) => {
                        return handledNodes.find(n => n[nodeType].id === curr[nodeType].id) ? prev : [...prev, curr];
                    }, []);
                });
                this.handleSpecDirEdges(curNodePos, allDirNodes, handledNodes, nodeType, curNodeCell);
            }
        },
        // 获取某个方向上，一些点中距离curNode最近的点
        getClosestPos (curNode, longest) {
            let closest = {};
            switch (longest.dir) {
            case "top":
                closest = longest.nodes.reduce((prev, curr) => curNode.y - curr.y <= curNode.y - prev.y ? curr : prev);
                break;
            case "right":
                closest = longest.nodes.reduce((prev, curr) => curr.x - curNode.x <= prev.x - curNode.x ? curr : prev);
                break;
            case "bottom":
                closest = longest.nodes.reduce((prev, curr) => curr.y - curNode.y <= prev.y - curNode.y ? curr : prev);
                break;
            case "left":
                closest = longest.nodes.reduce((prev, curr) => curNode.x - curr.x <= curNode.x - prev.x ? curr : prev);
                break;
            }
            return closest;
        },
        // 给某个方向的点设置拐点和路由
        setVerticesAndRouter (closest, item, dir, curNode, nodeType, curNodeCell, totalNodes) {
            // 处理过的边不再处理
            const edgeExist = this.handledEdges.find(e => e.edge.id === item.edge.id);
            if (edgeExist) {
                return;
            }

            let point = [];
            const itemNodePos = this.alterNodeConter(item[nodeType]);
            // 连线方向
            let edgeDir = [];
            if (dir === "top") {
                if (totalNodes.length > 1) {
                    const verticeY = this.getRound10(closest.y + this.node_height / 2 + 20);
                    point.push({ x: curNode.x, y: verticeY });
                    if (nodeType === "targetNode") {
                        point.push({ x: itemNodePos.x, y: verticeY });
                    }
                }

                edgeDir = ["top", "bottom"];
            }
            if (dir === "right") {
                const verticeX = this.getRound10(closest.x - this.node_width / 2 - 20);
                point.push({ x: verticeX, y: curNode.y });
                if (nodeType === "targetNode") {
                    point.push({ x: verticeX, y: itemNodePos.y });
                    
                    // 判断原拐点和目标节点之间有障碍物
                    point = this.findNewVertices(dir, verticeX, itemNodePos, point);
                }

                edgeDir = ["left", "right"];
            }
            if (dir === "bottom") {
                // 计算拐点的y坐标
                const verticeY = this.getRound10(closest.y - this.node_height / 2 - 20);
                point.push({ x: curNode.x, y: verticeY });
                if (nodeType === "targetNode") {
                    point.push({ x: itemNodePos.x, y: verticeY });

                    // 判断原拐点和目标节点间有无障碍物
                    point = this.findNewVertices(dir, verticeY, itemNodePos, point);
                }
                edgeDir = ["top", "bottom"];
            }
            if (dir === "left") {
                const pointX = this.getRound10(closest.x + this.node_width / 2 + 20);
                point.push({ x: pointX, y: curNode.y });
                if (nodeType === "targetNode") {
                    point.push({ x: pointX, y: itemNodePos.y });
                }
                edgeDir = ["left", "right"];
            }
            item.edge.setVertices(point).setRouter({
                name: "manhattan",
                args: {
                    padding: 8,
                    startDirections: edgeDir,
                    endDirections: edgeDir
                }
            });
            // 把这条边加进处理完的边数组中
            this.handledEdges.push({ edge: item.edge, dir });
        },
        // 当拐点和当前处理的节点之间有中间点时，寻找新的拐点
        findNewVertices (dir, vertice, itemNodePos, point) {
            // 定义矩形的左上角坐标，宽高
            let [areaX, areaY, areaW, areaH] = [0, 0, 0, 0];
            let nodesInMid = [];

            switch (dir) {
            case "top":
                areaX = itemNodePos.x - 20;
                areaY = vertice;
                areaW = 40;
                areaH = itemNodePos.y - this.node_height / 2 - 20 - vertice;
                // 获取原拐点与当前node之间有无障碍物
                nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                while (nodesInMid.length) {
                    // 如果有障碍物，判断该障碍物右侧有无障碍物
                    areaX = nodesInMid[0].position().x + this.node_width;
                    nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                    // 没有障碍物了，设置新拐点
                    if (!nodesInMid || !nodesInMid.length) {
                        point.pop();
                        point.push({
                            x: areaX + 20,
                            y: vertice
                        });
                    }
                }
                point.push({
                    x: itemNodePos.x,
                    y: this.getRound10(itemNodePos.y - this.node_height / 2 - 20)
                });
                break;
            case "bottom":
                areaX = itemNodePos.x - 20;
                areaY = vertice;
                areaW = 40;
                areaH = itemNodePos.y - this.node_height / 2 - 20 - vertice;
                // 获取原拐点与当前node之间有无障碍物
                nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                while (nodesInMid.length) {
                    // 如果有障碍物，判断该障碍物右侧有无障碍物
                    areaX = nodesInMid[0].position().x + this.node_width;
                    nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                    // 没有障碍物了，设置新拐点
                    if (!nodesInMid || !nodesInMid.length) {
                        point.pop();
                        point.push({
                            x: areaX + 20,
                            y: vertice
                        });
                    }
                }
                point.push({
                    x: itemNodePos.x,
                    y: this.getRound10(itemNodePos.y - this.node_height / 2 - 20)
                });
                break;
            case "left":
                break;
            case "right":
                areaX = vertice;
                areaY = itemNodePos.y - 20;
                areaW = itemNodePos.x - this.node_width / 2 - 20 - vertice;
                areaH = 40;
                // 获取原拐点与当前node之间有无障碍物
                nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                while (nodesInMid.length) {
                    // 如果有障碍物，判断该障碍物右侧有无障碍物
                    areaY = nodesInMid[0].position().y + this.node_height;
                    nodesInMid = this.graph.getNodesInArea(areaX, areaY, areaW, areaH);
                    // 没有障碍物了，设置新拐点
                    if (!nodesInMid || !nodesInMid.length) {
                        point.pop();
                        point.push({
                            x: vertice,
                            y: areaY + 20
                        });
                    }
                }
                point.push({
                    x: itemNodePos.x - this.node_width / 2 - 20,
                    y: itemNodePos.y
                });
                break;
            }
            return point;
        },
        // 获取第二个要处理的方向
        getOppoDir (curDir) {
            let dir = "";
            switch (curDir) {
            case "top": dir = "bottom";
                break;
            case "bottom": dir = "top";
                break;
            case "right": dir = "left";
                break;
            case "left": dir = "right";
                break;
            }
            return dir;
        },
        // 注册一些事件
        registEvents () {
            // 显示/隐藏事件
            const showPorts = (ports, show) => {
                for (let i = 0, len = ports.length; i < len; i++) {
                    ports[i].style.visibility = show ? "visible" : "hidden";
                }
            };
            // 鼠标移入，显示连接桩
            this.graph.on("node:mouseenter", ({ e, node, view }) => {
                const ports = this.$refs.container.querySelectorAll(".x6-port-body");
                showPorts(ports, true);

                const edges = this.graph.getConnectedEdges(node);
                edges.forEach(edge => {
                    edge.setAttrs({
                        line: {
                            stroke: "red"
                        }
                    });
                    edge.setZIndex(11);
                });
            });
            // 鼠标移出，隐藏连接桩
            this.graph.on("node:mouseleave", ({ e, node, view }) => {
                const ports = this.$refs.container.querySelectorAll(".x6-port-body");
                showPorts(ports, false);

                const edges = this.graph.getConnectedEdges(node);
                edges.forEach(edge => {
                    edge.setAttrs({
                        line: {
                            stroke: "#333333"
                        }
                    });
                    edge.setZIndex(1);
                });
            });

            // 节点单击
            this.graph.on("node:click", ({ e, x, y, node, view }) => {
                console.log(this.graph.getConnectedEdges(node));
            });
            // 节点移动
            this.graph.on("node:moving", ({ e, x, y, node }) => {
            });
            // 节点移动完成
            this.graph.on("node:moved", ({ e, x, y, node }) => {
                // 更新连线
                this.updateEdgesByVerticesAndRouter();
                console.log("node:moved");
            });

            // 连线完成
            this.graph.on("edge:connected", ({ isNew, edge }) => {
                // 把连线指向节点本身
                edge.setSource(edge.getSourceCell());
                edge.setTarget(edge.getTargetCell());
                // 更新连线
                this.updateEdgesByVerticesAndRouter();
            });

            // 连线单击
            this.graph.on("edge:click", ({ e, x, y, edge, view }) => {
                // console.log(`当前edge的id: ${edge}`);
                console.log(edge);
                console.log(`点击位置 x,y: ${x},${y}`);
                console.log("当前edge的路径点:");
                console.log(edge.getVertices());
            });

            // 双击
            this.graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {
                // 双击删除节点、连线
                this.graph.removeCell(cell);
            });

            // 画布点击事件
            this.graph.on("blank:click", ({ e, x, y }) => {
                console.log(x + ", " + y);
            });

            this.graph.on("render:done", () => {
                // console.log("render:done");
                // this.graph.centerContent();
            });
        },
        // 格式化
        format () {
            this.currDir = this.currDir === "LR" ? "TB" : "LR";
            this.setDagreLayout();
        },
        // 保存
        save () {
            const nodesArr = [];
            const nodes = this.graph.getNodes();
            nodes.forEach(node => {
                nodesArr.push({
                    id: node.id,
                    label: node.label,
                    ...node.position()
                });
            });

            const edgesArr = [];
            const edges = this.graph.getEdges();
            edges.forEach(edge => {
                edgesArr.push({
                    source: edge.getSource().cell,
                    target: edge.getTarget().cell
                });
            });
            const graphData = { nodesArr, edgesArr };
            localStorage.setItem("graphData", JSON.stringify(graphData));
        },
        // 转换节点position为节点中心点
        alterNodeConter (node) {
            return {
                x: node.position().x + this.node_width / 2,
                y: node.position().y + this.node_height / 2
            };
        },
        // 四舍五入获取10的倍数
        getRound10 (val) {
            return Math.round(val / 10) * 10;
        }
    },
    created () {
    },
    mounted () {
        this.getData();
        this.initGraph();
    }
};
</script>

<style scoped>
.wrapper {
    display: flex;
}
.btn-list {
    margin: 0 0 0 10px;
}
.btn {
    display: block;
    width: 50px;
    height: 20px;
    margin: 0 0 10px 0;
}

</style>
